package com.onlyxiahui.common.action.description.handler.impl;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;

import com.onlyxiahui.common.action.description.DocumentContext;
import com.onlyxiahui.common.action.description.bean.MethodData;
import com.onlyxiahui.common.action.description.bean.ModuleData;
import com.onlyxiahui.common.action.description.bean.ParameterData;
import com.onlyxiahui.common.action.description.bean.PropertyData;
import com.onlyxiahui.common.action.description.bean.ResultData;
import com.onlyxiahui.common.action.description.enums.data.JsonDataType;
import com.onlyxiahui.common.action.description.enums.data.ParameterType;
import com.onlyxiahui.common.action.description.handler.MethodHandler;
import com.onlyxiahui.common.action.description.handler.MethodTitleHandler;
import com.onlyxiahui.common.action.description.handler.ParameterHandler;
import com.onlyxiahui.common.action.description.handler.ResultHandler;
import com.onlyxiahui.common.action.description.util.ActionKeyUtil;
import com.onlyxiahui.common.action.description.util.ActionMd5Util;
import com.onlyxiahui.common.action.description.util.ActionTitleUtil;
import com.thoughtworks.qdox.model.JavaMethod;

/**
 * Description <br>
 * Date 2020-01-07 16:17:59<br>
 * 
 * @author XiaHui [onlovexiahui@qq.com]<br>
 * @since 1.0.0
 */

public abstract class BaseMethodHandler implements MethodHandler {

    /**
     * 
     * Description <br>
     * Date 2020-04-10 14:15:15<br>
     * 
     * @param context
     * @param moduleData
     * @param method
     * @param paths
     * @param combineAction 是否合并路径
     * @return
     * @since 1.0.0
     */
    public MethodData create(
            DocumentContext context,
            ModuleData moduleData,
            Method method,
            List<String> paths,
            boolean combineAction) {

        Class<?> moduleClass = method.getDeclaringClass();
        String methodName = method.getName();
        Parameter[] parameters = method.getParameters();

        String key = "";
        String name = methodName;
        String title = getMethodTitle(context, moduleData, method);
        // String action = "";
        String description = getMethodDescription(context, method);
        String className = moduleClass.getName();

        boolean ignore = context.getIgnoreJudgeBox().ignore(context, method);

        MethodData md = new MethodData();

        String pk = ActionKeyUtil.getKey(parameters);
        key = className + "." + methodName + "_" + pk;

        md.setKey(ActionMd5Util.lower32(key));
        md.setPaths(paths);
        md.setName(name);
        md.setClassName(className);
        md.setTitle(title);
        md.setDescription(description);
        md.setIgnore(ignore);

        if (combineAction) {
            List<String> modulePaths = moduleData.getPaths();
            Set<String> actions = combineAction(context, modulePaths, paths);
            md.setActions(actions);
        }

        for (ResultHandler h : context.getResultHandlers()) {
            if (h.support(context, moduleData, md, method)) {
                ResultData rd = h.handle(context, moduleData, md, method);
                if (null != rd) {
                    md.setResult(rd);
                    break;
                }
            }
        }
        return md;
    }

    public Set<String> combineAction(DocumentContext context, List<String> modulePaths, List<String> methodPaths) {
        if (null == modulePaths) {
            modulePaths = new ArrayList<>();
        }
        if (null == methodPaths) {
            methodPaths = new ArrayList<>();
        }

        Set<String> actions = new HashSet<>();

        if (modulePaths.isEmpty()) {
            for (String methodPath : methodPaths) {
                String path = context.getPathMatcher().combine("", methodPath);
                if (null != path && !path.isEmpty()) {
                    actions.add(path);
                }
            }
        } else {
            for (String modulePath : modulePaths) {
                for (String methodPath : methodPaths) {
                    String path = context.getPathMatcher().combine(modulePath, methodPath);
                    if (null != path && !path.isEmpty()) {
                        actions.add(path);
                    }
                }
            }
        }
        return actions;
    }

    protected MethodParameter[] initMethodParameters(Method method, Class<?> clazz) {
        int count = method.getParameterCount();
        MethodParameter[] result = new MethodParameter[count];
        for (int i = 0; i < count; i++) {
            SynthesizingMethodParameter parameter = SynthesizingMethodParameter.forExecutable(method, i);
            GenericTypeResolver.resolveParameterType(parameter, clazz);
            result[i] = parameter;
        }
        return result;
    }

    protected List<ActionMethod> getActionMethodList(Class<?> clazz) {
        List<ActionMethod> list = new ArrayList<>();
        Method[] methods = clazz.getMethods();
        if (null != methods && methods.length > 0) {
            for (Method method : methods) {
                list.add(new ActionMethod(clazz, method));
            }
        }
        return list;
    }

    public String getMethodTitle(DocumentContext context, ModuleData moduleData, Method method) {
        Class<?> moduleClass = method.getDeclaringClass();
        String methodName = method.getName();
        String className = moduleClass.getName();
        Parameter[] parameters = method.getParameters();

        JavaMethod jm = context.getSourceBox().getJavaMethodByName(className, methodName, parameters);
        String comment = "";
        if (null != jm) {
            comment = jm.getComment();
        }
        String title = null;

        for (MethodTitleHandler h : context.getMethodTitleHandlers()) {
            if ((null == title || title.isEmpty()) && h.support(context, moduleData, method, comment)) {
                title = h.handle(context, moduleData, method, comment);
            }
        }

        if (null == title || title.isEmpty()) {
            title = ActionTitleUtil.handle(comment);
        }

        if (null == title || title.isEmpty()) {
            title = methodName;
        }
        return null == title ? "" : title;
    }

    public String getMethodDescription(DocumentContext context, Method method) {
        Class<?> moduleClass = method.getDeclaringClass();
        String methodName = method.getName();
        String className = moduleClass.getName();
        Parameter[] parameters = method.getParameters();

        String description = "";
        JavaMethod jm = context.getSourceBox().getJavaMethodByName(className, methodName, parameters);
        if (null != jm) {
            description = jm.getComment();
        }
        return null == description ? "" : description;
    }

    public List<ParameterData> getMethodParameters(DocumentContext context, ModuleData moduleData, MethodData md, Method method) {
        Class<?> classType = method.getDeclaringClass();
        List<ParameterData> parameters = new ArrayList<>();
        MethodParameter[] mps = initMethodParameters(method, classType);
        for (MethodParameter parameter : mps) {
            for (ParameterHandler ph : context.getParameterHandlers()) {
                if (ph.support(context, moduleData, md, method, parameter)) {
                    ParameterData pd = ph.handle(context, moduleData, md, method, parameter);
                    if (null != pd) {
                        parameters.add(pd);
                    }
                }
            }
        }
        return parameters;
    }

    public List<ParameterData> handleRootMethodParameters(List<ParameterData> list) {
        List<ParameterData> parameters = new ArrayList<>();
        if (null != list) {
            int size = list.size();
            Map<String, PropertyData> rootPathMap = new HashMap<>(size);
            Map<String, PropertyData> pathMap = new HashMap<>(size);

            List<ParameterData> bodys = new ArrayList<>();
            for (ParameterData parameterData : list) {
                String type = parameterData.getParameterType();
                if (ParameterType.body.type().equals(type)) {
                    bodys.add(parameterData);
                    PropertyData propertyData = parameterData.getProperty();
                    if (null != propertyData) {
                        List<PropertyData> nodes = propertyData.getNodes();
                        if (null != nodes) {
                            for (PropertyData node : nodes) {
                                if (null != node) {
                                    String name = node.getName();
                                    if (null != name && !name.isEmpty()) {
                                        setRoot(rootPathMap, pathMap, node, name);
                                    }
                                }
                            }
                        }
                    }
                } else if (ParameterType.node.type().equals(type)) {
                    PropertyData propertyData = parameterData.getProperty();
                    if (null != propertyData) {
                        String name = propertyData.getName();
                        if (null != name && !name.isEmpty()) {
                            setRoot(rootPathMap, pathMap, propertyData, name);
                        }
                    }
                } else {
                    parameters.add(parameterData);
                }
            }

            List<PropertyData> nodes = new ArrayList<>();
            for (PropertyData pd : rootPathMap.values()) {
                nodes.add(pd);
            }

            if (!nodes.isEmpty()) {
                if (!bodys.isEmpty()) {
                    ParameterData parameterData = bodys.get(0);
                    PropertyData propertyData = parameterData.getProperty();
                    if (null != propertyData) {
                        propertyData.setNodes(nodes);
                    } else {
                        propertyData = new PropertyData();

                        String name = "";
                        String description = "";
                        String type = JsonDataType.Object.type();
                        String className = Object.class.getName();

                        propertyData.setName(name);
                        propertyData.setType(type);
                        propertyData.setDescription(description);
                        propertyData.setClassName(className);

                        propertyData.setNodes(nodes);
                    }
                    parameters.add(parameterData);
                } else {

                    PropertyData pd = new PropertyData();

                    String name = "";
                    String description = "";
                    String type = JsonDataType.Object.type();
                    String className = Object.class.getName();

                    pd.setName(name);
                    pd.setType(type);
                    pd.setDescription(description);
                    pd.setClassName(className);

                    pd.setNodes(nodes);

                    ParameterData root = new ParameterData(ParameterType.body.type(), pd);
                    parameters.add(root);
                }
            }
        }
        return parameters;
    }

    public Map<String, Object> getPathMap(String pathName) {
        Map<String, Object> pathMap = new HashMap<>();
        if (null != pathName) {
            String[] paths = pathName.split("[.]");
            int length = paths.length;
            int last = length - 1;
            Map<String, Object> superMap = pathMap;
            for (int i = 0; i < length; i++) {
                String path = paths[i];
                if (i == last) {
                    superMap.put(path, "");
                } else {
                    Map<String, Object> nodeMap = new HashMap<>();
                    superMap.put(path, nodeMap);
                    superMap = nodeMap;
                }
            }
        }
        return pathMap;
    }

    @SuppressWarnings("unchecked")
    public void setRoot(Map<String, PropertyData> rootPathMap, Map<String, PropertyData> pathMap, PropertyData pd, String pathName) {
        Map<String, Object> o = getPathMap(pathName);
        Set<String> keySet = o.keySet();
        for (String key : keySet) {
            Object v = o.get(key);
            if (v instanceof Map) {
                PropertyData superData = pathMap.get(key);
                if (null == superData) {
                    superData = new PropertyData();

                    String name = key;
                    String description = "";
                    String type = JsonDataType.Object.type();
                    String className = "map";

                    superData.setName(name);
                    superData.setType(type);
                    superData.setDescription(description);
                    superData.setClassName(className);

                    pathMap.put(key, superData);
                }

                PropertyData data = rootPathMap.get(key);
                if (null == data) {
                    data = superData;
                    rootPathMap.put(key, data);
                }
                Map<String, Object> jo = (Map<String, Object>) v;
                setNode(key, key, pathMap, jo, superData, pd);
            } else {
                pd.setName(key);
                rootPathMap.put(key, pd);
            }
        }
    }

    @SuppressWarnings("unchecked")
    public void setNode(String superKey, String superPath, Map<String, PropertyData> pathMap, Map<String, Object> o, PropertyData superData, PropertyData data) {

        Set<String> keySet = o.keySet();
        for (String key : keySet) {
            String path = superPath + "." + key;
            Object v = o.get(key);
            if (v instanceof Map) {
                PropertyData nextSuperData = pathMap.get(path);
                if (null == nextSuperData) {
                    String name = key;
                    String description = "";
                    String type = JsonDataType.Object.type();
                    String className = "map";

                    nextSuperData = new PropertyData();
                    nextSuperData.setName(name);
                    nextSuperData.setType(type);
                    nextSuperData.setDescription(description);
                    nextSuperData.setClassName(className);

                    pathMap.put(path, nextSuperData);

                    List<PropertyData> nodes = superData.getNodes();
                    if (null == nodes) {
                        nodes = new ArrayList<>();
                        superData.setNodes(nodes);
                    }
                    nodes.add(nextSuperData);
                }

                Map<String, Object> jo = (Map<String, Object>) v;
                setNode(key, path, pathMap, jo, nextSuperData, data);
            } else {
                data.setName(key);
                List<PropertyData> nodes = superData.getNodes();
                if (null == nodes) {
                    nodes = new ArrayList<>();
                    superData.setNodes(nodes);
                }
                nodes.add(data);
            }
        }
    }

    protected class ActionMethod {

        private Class<?> actionClass;
        private Method method;
        private MethodParameter[] methodParameters;

        public ActionMethod(Class<?> actionClass, Method method) {
            this.actionClass = actionClass;
            this.method = method;
            initialize(method);
        }

        private void initialize(Method method) {
            methodParameters = initMethodParameters(method);
        }

        private MethodParameter[] initMethodParameters(Method method) {
            int count = method.getParameterCount();
            MethodParameter[] result = new MethodParameter[count];
            for (int i = 0; i < count; i++) {
                SynthesizingMethodParameter parameter = SynthesizingMethodParameter.forExecutable(method, i);
                GenericTypeResolver.resolveParameterType(parameter, this.actionClass);
                result[i] = parameter;
            }
            return result;
        }

        public Class<?> getActionClass() {
            return actionClass;
        }

        public Method getMethod() {
            return method;
        }

        public MethodParameter[] getMethodParameters() {
            return methodParameters;
        }
    }
}
